import { Injectable, UnauthorizedException, ConflictException, ForbiddenException } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { JwtService } from '@nestjs/jwt'
import { User } from 'src/modules/user/entities/user.entity'
import { LoginDto } from '../dto/login.dto'
import { RegisterDto } from '../dto/register.dto'
import { RbacService } from 'src/modules/rbac/services/rbac.service'
import { JwtPayload, LoginPayload, Payload } from 'src/common/interfaces/auth.interface'
import { Inject } from '@nestjs/common'
import { Cache } from 'cache-manager'
import { CACHE_MANAGER } from '@nestjs/cache-manager'
import config from 'src/config'
import { timeToMs } from 'src/shared/utils/time-utils'

/**
 * JWT令牌负载接口定义
 * @interface Payload
 * @property {number} sub - 用户ID
 * @property {string} username - 用户名
 * @property {string} email - 用户邮箱
 * @property {string} nickname - 用户昵称
 * @property {string} avatar - 用户头像
 * @property {string} phone - 用户手机号
 * @property {boolean} isSuperAdmin - 是否为超级管理员
 * @property {string[]} permissions - 权限列表
 */

/**
 * 认证服务类，处理用户登录、注册、令牌刷新等认证相关操作
 */
@Injectable()
export class AuthService {
  constructor(
    @InjectRepository(User)
    private readonly userRepo: Repository<User>,
    private readonly jwtService: JwtService,
    private readonly rbacService: RbacService,
    @Inject(CACHE_MANAGER)
    private readonly cacheManager: Cache,
  ) {}

  // 支持用户名或邮箱登录
  /**
   * 验证本地用户身份
   * @param usernameOrEmail - 用户名或邮箱
   * @param password - 密码
   * @returns 验证成功的用户对象
   * @throws {UnauthorizedException} 用户不存在、已禁用或密码错误时抛出
   */
  async validateLocalUser(usernameOrEmail: string, password: string): Promise<User> {
    // 检查用户是否被锁定
    const lockKey = `login_lock:${usernameOrEmail}`
    const isLocked = await this.cacheManager.get<boolean>(lockKey)

    if (isLocked) {
      throw new UnauthorizedException(`账户已被锁定，请${config.jwt.loginLockTime}分钟后再试`)
    }

    // 获取失败尝试次数
    const attemptsKey = `login_attempts:${usernameOrEmail}`
    let attempts = (await this.cacheManager.get<number>(attemptsKey)) || 0

    const user = await this.userRepo.findOne({
      where: [{ email: usernameOrEmail }, { username: usernameOrEmail }],
      select: [
        'id',
        'username',
        'email',
        'password',
        'status',
        'nickname',
        'avatar',
        'phone',
        'lastLoginAt',
        'createdAt',
        'updatedAt',
      ],
    })

    if (!user || !user.status) {
      // 即使用户不存在，也增加尝试次数以防止暴力破解
      attempts++
      await this.cacheManager.set(attemptsKey, attempts, 60000 * config.jwt.loginLockTime)

      if (attempts >= config.jwt.maxLoginAttempts) {
        await this.cacheManager.set(lockKey, true, 60000 * config.jwt.loginLockTime)
        throw new UnauthorizedException(`账户已被锁定，请${config.jwt.loginLockTime}分钟后再试`)
      }

      throw new UnauthorizedException('用户不存在或已禁用')
    }

    const isValid = await user.validatePassword(password)

    if (!isValid) {
      attempts++
      await this.cacheManager.set(attemptsKey, attempts, 60000 * config.jwt.loginLockTime)

      if (attempts >= config.jwt.maxLoginAttempts) {
        await this.cacheManager.set(lockKey, true, 60000 * config.jwt.loginLockTime)
        throw new UnauthorizedException(`账户已被锁定，请${config.jwt.loginLockTime}分钟后再试`)
      }

      throw new UnauthorizedException(`账号或密码错误，剩余尝试次数: ${config.jwt.maxLoginAttempts - attempts}`)
    }

    // 登录成功，重置尝试次数
    await this.cacheManager.del(attemptsKey)
    await this.cacheManager.del(lockKey)

    // 更新最后登录时间
    user.lastLoginAt = new Date()
    await this.userRepo.save(user)

    return user
  }

  /**
   * 用户登录接口
   * @param dto - 登录数据传输对象
   * @returns 包含访问令牌、刷新令牌和用户基本信息的对象
   */
  async login(dto: LoginDto): Promise<LoginPayload> {
    const user = await this.validateLocalUser(dto.usernameOrEmail, dto.password)

    const payload: Payload = {
      id: user.id,
      username: user.username,
      email: user.email,
      nickname: user.nickname,
      avatar: user.avatar,
      phone: user.phone,
      isSuperAdmin: await this.rbacService.isSuperAdmin(user.id),
      roles: await this.rbacService.getUserRoles(user.id),
    }

    const access_token = this.jwtService.sign(payload, {
      secret: config.jwt.secret,
      expiresIn: config.jwt.accessTokenExpiresIn,
    })
    const refresh_token = this.jwtService.sign(payload, {
      secret: config.jwt.secret,
      expiresIn: config.jwt.refreshTokenExpiresIn,
    })

    // 存储刷新令牌到缓存，实现单设备登录
    const tokenKey = `refresh_token:${user.id}`
    await this.cacheManager.set(tokenKey, refresh_token, timeToMs(config.jwt.refreshTokenExpiresIn))

    return {
      access_token,
      refresh_token,
      user: payload,
    }
  }

  /**
   * 刷新访问令牌
   * @param refreshToken - 刷新令牌
   * @returns 新的访问令牌
   * @throws {UnauthorizedException} 刷新令牌无效或已过期时抛出
   */
  async refresh(refreshToken: string) {
    try {
      // 验证刷新令牌
      const payload = this.jwtService.verify<JwtPayload>(refreshToken, {
        secret: config.jwt.secret,
      })

      // 检查用户是否存在且启用
      const user = await this.userRepo.findOne({
        where: { id: payload.id },
        select: ['id', 'status'],
      })

      if (!user || !user.status) {
        throw new ForbiddenException('用户不存在或已禁用')
      }

      // 检查刷新令牌是否已被撤销（可选：实现单设备登录）
      const tokenKey = `refresh_token:${user.id}`
      const storedToken = await this.cacheManager.get<string>(tokenKey)

      if (!storedToken || storedToken !== refreshToken) {
        throw new UnauthorizedException('刷新令牌已失效，请重新登录')
      }
      // 移除payload中的exp属性，避免与expiresIn选项冲突
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      const { exp, iat, ...effectivePayload } = payload
      // 生成新的访问令牌
      const access_token = this.jwtService.sign(effectivePayload, {
        secret: config.jwt.secret,
        expiresIn: config.jwt.accessTokenExpiresIn,
      })
      return access_token
    } catch (e) {
      // 区分不同类型的错误
      if (e instanceof Error && e.name === 'TokenExpiredError') {
        throw new UnauthorizedException('刷新令牌已过期，请重新登录')
      } else if (e instanceof Error && e.name === 'JsonWebTokenError') {
        throw new UnauthorizedException('刷新令牌无效')
      }
      throw new UnauthorizedException('刷新令牌验证失败')
    }
  }

  /**
   * 用户注册接口
   * @param dto - 注册数据传输对象
   * @returns 新创建用户的基本信息
   * @throws {ConflictException} 邮箱或用户名已存在时抛出
   */
  async register(dto: RegisterDto) {
    // 检查用户名和邮箱是否已存在
    const exist = await this.userRepo.findOne({
      where: [{ email: dto.email }, { username: dto.username }],
      select: ['id', 'username', 'email'],
    })

    if (exist) {
      if (exist.email === dto.email) {
        throw new ConflictException('邮箱已被注册')
      }
      throw new ConflictException('用户名已存在')
    }

    // 创建用户并加密密码
    const user = this.userRepo.create({
      ...dto,
      // 密码将通过User实体的setter自动加密
    })

    await this.userRepo.save(user)

    // 注册成功后，为新用户分配默认角色
    // 注意：这里默认分配'user'角色
    try {
      await this.rbacService.assignRolesToUserByRoleNames(user.id, ['user'])
    } catch (error) {
      // 记录错误但不阻止注册流程
      console.error('为新用户分配默认角色失败:', error)
    }

    return {
      id: user.id,
      username: user.username,
      email: user.email,
    }
  }

  /**
   * 验证JWT令牌负载
   * @param payload - JWT令牌负载
   * @returns 验证成功的用户对象
   * @throws {UnauthorizedException} 用户不存在或已禁用时抛出
   */
  async validateJwtPayload(payload: Payload): Promise<User> {
    const user = await this.userRepo.findOne({
      where: { id: payload.id },
      select: ['id', 'username', 'email', 'status'],
    })
    if (!user || !user.status) {
      throw new UnauthorizedException('用户不存在或已禁用')
    }
    return user
  }

  /**
   * 获取用户个人资料
   * @param userId - 用户ID
   * @returns 用户详细信息
   * @throws {UnauthorizedException} 用户不存在时抛出
   */
  async getProfile(userId: number): Promise<User> {
    const user = await this.userRepo.findOne({ where: { id: userId } })
    if (!user) throw new UnauthorizedException('用户不存在')
    return user
  }

  /**
   * 获取用户信息，包括role和permission
   * @param userId - 用户ID
   * @returns 用户信息，包括role和permission
   */
  async getUserInfo(userId: number): Promise<Payload> {
    const user = await this.userRepo.findOne({ where: { id: userId }, relations: ['roles'] })
    if (!user) throw new UnauthorizedException('用户不存在')
    return {
      id: user.id,
      username: user.username,
      email: user.email,
      nickname: user.nickname,
      avatar: user.avatar,
      phone: user.phone,
      isSuperAdmin: await this.rbacService.isSuperAdmin(userId),
      roles: user.roles,
    }
  }
}
